#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header File For sleeping.
#include <stdio.h>
#include <string.h>
#include "../libcg/cg.h"
#include "../libcg/utils.h"

/* ASCII code for the escape key. */
#define ESCAPE 27

/* The number of our GLUT window */
int window; 

/* Size defines */
#define SIZE 10000
#define INIT_ACTUAL_SIZE 100
#define ACTUAL_STEP 100
#define MAX_SIZE SIZE
#define MIN_SIZE 0

/* Select axis of rotation */
#define X 0
#define Y 1
#define Z 2
#define INIT_AXIS X

#define ANGLE 0.001f

int actual_size;
int actual_axis;
cgLine lines[SIZE];
RotationMatrix rm;

void set_rot_matrix(void)
{
    switch (actual_axis)
    {
    case X:
	cg_rot_matrix_x(&rm, ANGLE);
	break;
    case Y:
	cg_rot_matrix_y(&rm, ANGLE);
	break;
    case Z:
	cg_rot_matrix_z(&rm, ANGLE);
	break;
    }
}

void reset_state(void)
{
    cg_random_lines(lines, SIZE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
    actual_size = INIT_ACTUAL_SIZE;
    actual_axis = INIT_AXIS;
    set_rot_matrix();
}

void init_gl(int Width, int Height)
// We call this right after our OpenGL window is created.
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, (GLfloat)Width/(GLfloat)Height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);
    reset_state();
}

void resize_gl_scene(int width, int height)
{
    if (height==0)  // Prevent A Divide By Zero If The Window Is Too Small
	height=1;

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
}

void draw_axis(void)
{
    glBegin(GL_LINES);
    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(1.0f, 0.0f, 0.0f);

    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);

    glColor3f(0.0, 0.0, 1.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 1.0f);
    glEnd();
}

void draw_lines()
{
    int i, j;

    cg_rotate_lines(lines, actual_size, &rm);
    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINES);
    for (i = 0; i < actual_size; i++)
    	for (j = 0; j < 2; j++)
	    glVertex3f(lines[i].p[j].c[0],
		       lines[i].p[j].c[1], 
		       lines[i].p[j].c[2]);
    glEnd();
}

/* The main drawing function. */
void draw_gl_scene()
{
    // Clear The Screen And The Depth Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();  // Reset The View

    glTranslatef(0.0f,0.0f,-8.0f);
    draw_axis();
    draw_lines();

    // swap buffers to display, since we're double buffered.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void key_pressed(unsigned char key, int x, int y) 
{
    /* avoid thrashing this procedure */
    usleep(100);

    /* If escape is pressed, kill everything. */
    switch (key) 
    { 
    case ESCAPE:
        /* shut down our window */
	glutDestroyWindow(window); 
	/* exit the program...normal termination. */
	exit(0);
	break;
    case 'w':
	actual_size += ACTUAL_STEP;
	if (actual_size > MAX_SIZE)
	    actual_size = MAX_SIZE;
	break;
    case 's':
	actual_size -= ACTUAL_STEP;
	if (actual_size < MIN_SIZE)
	    actual_size = MIN_SIZE;
	break;
    case 'r':
	reset_state();
	break;
    case 'x': case 'y': case 'z':
	actual_axis = key - 'x';  // look at axis defines at top of file
	set_rot_matrix();
	break;
    }
}

int main(int argc, char **argv) 
{  
    glutInit(&argc, argv);  

    /* Select type of Display mode:   
       Double buffer 
       RGBA color
       Alpha components supported 
       Depth buffer */  
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

    /* get a 640 x 480 window */
    glutInitWindowSize(640, 480);  

    /* the window starts at the upper left corner of the screen */
    glutInitWindowPosition(0, 0);  

    /* Open a window */  
    window = glutCreateWindow("Rotate");  

    /* Register the function to do all our OpenGL drawing. */
    glutDisplayFunc(&draw_gl_scene);  

    /* Go fullscreen.  This is the soonest we could possibly go fullscreen. */
//    glutFullScreen();

    /* Even if there are no events, redraw our gl scene. */
    glutIdleFunc(&draw_gl_scene);

    /* Register the function called when our window is resized. */
    glutReshapeFunc(&resize_gl_scene);

    /* Register the function called when the keyboard is pressed. */
    glutKeyboardFunc(&key_pressed);

    /* Initialize our window. */
    init_gl(640, 480);
  
    /* Start Event Processing Engine */  
    glutMainLoop();  

    return 1;
}
